<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>类层次与抽象类</title>
</head>

<body>
  <script>
    //AbstractSet类只定义了一个抽象方法has（）
    class AbstractSet {
      // 抛出错误，强制子类必须定义这个方法的可用版本
      has(x) { throw new Error("Abstract method") }
    }
    let a1 = new AbstractSet()
    // console.log(a1.has(1));
    /*
    NotSet是AbstractSet的一个具体子类
    这个集合的成员是不属于其他集合的任何值
    因为使用另一个集合定义，所以它不可写
    而且因为他有无限个成员，所以不可枚举
    这个类只支持检测成员关系和使用数学符号
    把集合转换为字符串
    */
    class NotSet extends AbstractSet {
      constructor(set) {
        super();
        this.set = set
      }
      // 实现继承的抽象方法
      has(x) {
        console.log(this.set);
        return !this.set.has(x);
      }
      // 同时覆盖Object的方法
      toString() { return `{x|x∉${this.set.toString()}}` }
    }
    let a2 = { a: 1, b: 2, has(x) { return x == a2.a || x == a2.b } }
    let a = new NotSet(a2)
    console.log(a.has(2));
    console.log(a.toString());
    /*
    Range集合是AbstractSet的一个具体子类
    这个集合的成员是介于from和to（舍二者）之间的所有值
    由于其成员可能是浮点数值，因此不可枚举，也不具备有意义的大小
    */
    class RangeSet extends AbstractSet {
      constructor(from, to) {
        super()
        this.from = from;
        this.to = to
      }
      has(x) { return x >= this.from && x <= this.to }
      toString() { return `x|${this.from}<=x<=${this.to}` }
    }
    let b = new RangeSet(1, 3)
    // console.log(b.has(2));
    // console.log(b.toString());

    /*
    AbstractEnumerableSet是AbstrSet的一个抽象子类
    这个抽象类定义了一个抽象的获取方法，返回集合的大小，还定义了一个抽象迭代器。然后再次基础上实现了具体的isEmpty()、toString()和equals()方法。实现这个迭代器、大小获取方法以及（继承的）has()方法的子类，无偿获得这些具体方法
    */
    class AbstractEnumerableSet extends AbstractSet {
      get size() { throw new Error("Abstract method") }
      [Symbol.iterator]() { throw new Error("Abstract method") }
      isEmpty() { return this.size === 0 }
      toString() { return `{${Array.from(this).join(",")}}` }
      equals(set) {
        // 如果另一个集合不是AbstractEnumerableSet,那肯定不等于当前集合
        if (!(set instanceof AbstractEnumerableSet)) return false;

        // 如果两个集合大小不一样，它们也不相等
        if (this.size !== set.size) return false

        // 循环检查集合的元素
        for (let element of this) {
          // 只要有一个元素不在另一个集合中，它们就不相等
          if (!set.has(element)) return false
        }
        // 元素匹配，因此两个集合相等
        return true
      }
    }
    console.log(new AbstractEnumerableSet);

    /*
    SingletonSet是AbstractEnumerableSet的一个具体子类
    单体集合是只有一个成员的只读集合
    */
    class SingletonSet extends AbstractEnumerableSet {
      constructor(member) {
        super();
        this.member = member
      }
      // 实现3个抽象方法，同时继承基于这3个方法实现的
      // isEmpty()、equals()和toString()
      has(x) { return x === this.member }
      get size() { return 1 }
      *[Symbol.iterator]() { yield this.member }
    }
    let c = new SingletonSet(2)
    console.log(c.toString());

    /*
    AbstractWritableSet是AbstractEnumerableSet的一个抽象子类
    这个抽象类定义了抽象方法insert（）和remove（），分别用于插入和删除个别集合元素，然后在此基础上实现了具体的add（）、subtract（）、intersect（）方法。注意，我们的API从这里开始偏离了标准的JavaScript Set类
    */
    class AbstractWritableSet extends AbstractEnumerableSet {
      insert(x) { throw new Error("Abstract method") }
      remove(x) { throw new Error("Abstract method") }

      add(set) {
        for (let element of set) {
          this.insert(element)
        }
      }
      subtract(set) {
        for (let element of set) {
          this.remove(element)
        }
      }
      intersect(set) {
        for (let element of set) {
          if (!set.has(element)) {
            this.remove(element)
          }
        }
      }
    }
    let d = [[1, 2, 3, 4, 5]]
    let d1 = new AbstractWritableSet
    // console.log(d1.add(d));

    /*
    BitSet是AbstractWritableSet的一个具体子类
    这个类是非常高效的固定大小集合的实现
    用于元素为小于某个最大值的非负整数集合
    */
    class BitSet extends AbstractWritableSet {
      constructor(max) {
        super()
        this.max = max   //可存储的最大整数
        this.n = 0       //集合中整数的个数
        this.numBytes = Math.floor(max / 8) + 1   //需要多少字节
        this.data = new Uint8Array(this.numBytes)  //实际的字节
      }
      // 内部方法，检测一个值是否为当前集合的合法成员
      _valid(x) { return Number.isInteger(x) && x >= 0 && x <= this.max }

      // 测试数据数组中指定字节的指定位是否有值
      // 返回true或false
      _has(byte, bit) {
        // console.log(byte);
        // console.log(this.data)
        // console.log(BitSet.bits[bit]);;
        // console.log(this.data[byte]);
        return (this.data[byte] & BitSet.bits[bit] !== 0)
      }

      // x在这个BitSet中吗？
      has(x) {
        if (this._valid(x)) {
          let byte = Math.floor(x / 8)
          let bit = x % 8
          return this._has(byte, bit)
        } else {
          return false
        }
      }
      // 把x插入当前BitSet
      insert(x) {
        if (this._valid(x)) {           //如果这个值有效
          let byte = Math.floor(x / 8)  //转换为字节和位
          let bit = x % 8
          if (!this._has(byte, bit)) {  //如果对应的位没有值
            this.data[byte] |= BitSet.bits[bit];  //设置该位的值
            this.n++                               //并递增集合大小
          }
        } else {
          throw new TypeError("Invalid set element:" + x)
        }
      }

      remove(x) {
        if (this._valid(x)) {             //如果这个值有效
          let byte = Math.floor(x / 8)    //则计算字节和位
          let bit = x % 8
          if (this._has(byte, bit)) {  //如果该位已经设置了值
            this.data[byte0] |= BitSet.bits[bit];  //设置该位的值
            this.n--                               //并递增集合大小
          }
        } else {
          throw new TypeError("Invalid set element:" + x)
        }
      }

      // 获取方法，返回集合大小
      get size() { return this.n }

      // 迭代集合，只依次检查每一位
      // （应该可以更聪明一点，大幅优化这里的逻辑）
      *[Symbol.iterator]() {
        for (let i = 0; i <= this.max; i++) {
          if (this.has(i)) {
            yield i
          }
        }
      }
    }
    // has()、insert()和remove()方法会用的几个预定义值

    BitSet.bits = new Uint8Array([1, 2, 4, 8, 16, 32, 64, 128]);
    // console.log(BitSet.bits);
    BitSet.masks = new Uint8Array([~1, ~2, ~4, ~8, ~16, ~32, ~64, ~128])

    console.log(new BitSet(20).insert(16));
    console.log(new BitSet(20).has(16));
    console.log(new BitSet(20)._valid(16));
  </script>
</body>

</html>